跳到主要内容

SpringCloud Hystrix 配置线程池

信号量、线程池

参考资料 隔离技术线程池(ThreadPool)和信号量(semaphore)

Hystrix 内部提供了两种模式执行逻辑:信号量、线程池。默认情况 Hystrix 采用的是线程池

可以通过 Thread.currentThread().getName() 查看当前的线程

隔离方式是否支持超时是否支持熔断隔离原理是否是异步调用资源消耗
线程池隔离支持,可直接返回支持,当线程池到达maxSize后,再请求会触发fallback接口进行熔断每个服务单独用线程池可以是异步,也可以是同步。看调用的方法大,大量线程的上下文切换,容易造成机器负载高
信号量隔离不支持,如果阻塞,只能通过调用协议(如:socket超时才能返回)支持,当信号量达到 maxConcurrentRequests 后。再请求会触发 fallback通过信号量的计数器同步调用,不支持异步小,只是个计数器

信号量模式(SEMAPHORE)

在该模式下,接收请求和执行下游依赖在同一个线程内完成,不存在线程上下文切换所带来的性能开销,如果调用多个下游服务时也是同一个线程,也就是说,每次调用都得阻塞调用方的线程,直到结果返回。这样就导致了无法对访问做超时(只能依靠调用协议超时,无法主动释放)

另外,为了限制对下游依赖的并发调用量,可以配置 Hystrix 的 execution.isolation.semaphore.maxConcurrentRequests ,当并发请求数达到阈值时,请求线程可以快速失败,执行降级

实现也很简单,一个简单的计数器,当请求进入熔断器时,执行 tryAcquire() ,计数器加 1,结果大于阈值的话,就返回 false,发生信号量拒绝事件,执行降级逻辑。当请求离开熔断器时,执行 release(),计数器减 1。

线程池模式

接收用户请求采用 Tomcat 的线程池,而到了执行业务代码、调用其他服务时,则采用 Hystrix 的线程池(Hystrix 会搞很多个小小的线程池,比如订单服务请求库存服务是一个线程池,请求仓储服务是一个线程池,请求积分服务是一个线程池。每个线程池里的线程就仅仅用于请求那个服务。这样某个服务挂了也只是这个服务的线程池莫得了,其他的线程池还是能正常工作)

在该模式下,用户请求会被提交到 各自的线程池中执行,把执行每个下游服务的线程分离,从而达到资源隔离的作用。当线程池来不及处理并且请求队列塞满时,新进来的请求将快速失败,可以避免依赖问题扩散。

在信号量模式提到的问题,对所依赖的多个下游服务,通过线程池的异步执行,可以有效的提高接口性能。

优势

减少所依赖服务发生故障时的影响面,比如 ServiceA 服务发生异常,导致请求大量超时,对应的线程池被打满,这时并不影响ServiceB、ServiceC 的调用。 如果接口性能有变动,可以方便的动态调整线程池的参数或者是超时时间(前提是 Hystrix 参数实现了动态调整)

缺点

请求在线程池中执行,肯定会带来任务调度、排队和上下文切换带来的开销。 因为涉及到跨线程,那么就存在 ThreadLocal 数据的传递问题,比如在主线程初始化的 ThreadLocal 变量,在线程池线程中无法获取

下面的配置项(命令模式)默认在 HystrixCommandProperties 类里

可以自己设置隔离策略

设置项:THREADSEMAPHORE(信号量)

// name = execution.isolation.strategy

// to use thread isolation
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD)
// to use semaphore isolation
HystrixCommandProperties.Setter()
.withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE)

线程池的配置

参考资料 Hystrix 线程隔离技术解析-线程池 配置文档

如果使用 Tomcat 的线程池去接收用户的请求(Tomcat 是一个线程一个请求,即大力出奇迹),如果某一个服务出现了故障,这个时候后续有大量的请求过来,那么容器中的线程数量则会持续增加直致 CPU 资源耗尽到 100% (雪崩),导致 Tomcat 无法处理其他业务功能

Hystrix 线程池的配置(常用的) 更多的命令可以看文档

1、执行超时时间(只针对线程池) Default Value:1000

// name = execution.isolation.thread.timeoutInMilliseconds

HystrixCommandProperties.Setter()
.withExecutionTimeoutInMilliseconds(int value)

2、启动超时时间配置(默认开启)

// name = execution.timeout.enabled

HystrixCommandProperties.Setter()
.withExecutionTimeoutEnabled(boolean value)

3、超时时是否中断线程(默认 true)

// name = execution.isolation.thread.interruptOnTimeout

HystrixCommandProperties.Setter()
.withExecutionIsolationThreadInterruptOnTimeout(boolean value)

4、取消之后是否中断线程(默认 false)

// name = execution.isolation.thread.interruptOnCancel

HystrixCommandProperties.Setter()
.withExecutionIsolationThreadInterruptOnCancel(boolean value)

如何配置

@GetMapping("/search/{id}")
@HystrixCommand(fallbackMethod = "findByIdFallBack" ,commandProperties = {
// 设置隔离策略
@HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
// 设置超时时间
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000"),
})
public Customer findById(@PathVariable Integer id) {
return searchClient.findById(id);
}

// findById 的降级方法,方法描述要和接口一致
public Customer findByIdFallBack(@PathVariable Integer id) {
return new Customer(-1, "", 0);
}

信号量的配置

最大并发请求(默认 10)

// name = execution.isolation.semaphore.maxConcurrentRequests

HystrixCommandProperties.Setter()
.withExecutionIsolationSemaphoreMaxConcurrentRequests(int value)